#!/bin/bash
#
# Resource script for vrouter namespace support
#
# Description:  Manages vrouter namespace as an OCF resource in
#               an High Availability setup
#
# Vrouter OCF script's Author: Mirantis
# License: GNU General Public License (GPL)
#
#  usage: $0 {start|stop|restart|status|monitor|validate-all|meta-data}
#
#  The "start" arg starts vrouter.
#
#  The "stop" arg stops it.
#
# OCF parameters:
# OCF_RESKEY_ns
# OCF_RESKEY_other_networks
#
# OCF_RESKEY_host_interface
# OCF_RESKEY_namespace_interface
# OCF_RESKEY_host_ip
# OCF_RESKEY_namespace_ip
# OCF_RESKEY_network_mask
# OCF_RESKEY_route_metric
#
##########################################################################
# Initialization:

OCF_ROOT_default="/usr/lib/ocf"

OCF_RESKEY_ns_default="vrouter"
OCF_RESKEY_other_networks_default=false
OCF_RESKEY_host_interface_default="vrouter-host"
OCF_RESKEY_namespace_interface_default="vr-ns"
OCF_RESKEY_host_ip_default="240.0.0.5"
OCF_RESKEY_namespace_ip_default="240.0.0.6"
OCF_RESKEY_network_mask_default="30"
OCF_RESKEY_route_metric_default="10000"

: ${OCF_ROOT=${OCF_ROOT_default}}

: ${OCF_RESKEY_ns=${OCF_RESKEY_ns_default}}
: ${OCF_RESKEY_other_networks=${OCF_RESKEY_other_networks_default}}
: ${OCF_RESKEY_host_interface=${OCF_RESKEY_host_interface_default}}
: ${OCF_RESKEY_namespace_interface=${OCF_RESKEY_namespace_interface_default}}
: ${OCF_RESKEY_host_ip=${OCF_RESKEY_host_ip_default}}
: ${OCF_RESKEY_namespace_ip=${OCF_RESKEY_namespace_ip_default}}
: ${OCF_RESKEY_network_mask=${OCF_RESKEY_network_mask_default}}
: ${OCF_RESKEY_route_metric=${OCF_RESKEY_route_metric_default}}

: ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat}
. ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs

USAGE="Usage: $0 {start|stop|restart|status|monitor|validate-all|meta-data}";

RUN_IN_NS="ip netns exec $OCF_RESKEY_ns "
if [[ -z $OCF_RESKEY_ns ]] ; then
  RUN=''
else
  RUN="$RUN_IN_NS "
fi

##########################################################################

usage()
{
  echo $USAGE >&2
}

meta_data()
{
cat <<END
<?xml version="1.0"?>
<!DOCTYPE resource-agent SYSTEM "ra-api-1.dtd">
<resource-agent name="vrouter">
<version>1.0</version>
<longdesc lang="en">
This script manages vrouter daemon with namespace support
</longdesc>
<shortdesc lang="en">Manages an vrouter daemon inside an namespace</shortdesc>

<parameters>

<parameter name="ns">
<longdesc lang="en">
Name of network namespace.
Should be present.
</longdesc>
<shortdesc lang="en">Name of network namespace.</shortdesc>
<content type="string" default="${OCF_RESKEY_ns_default}"/>
</parameter>

<parameter name="other_networks">
<longdesc lang="en">
Additional routes that should be added to this resource. Routes will be added via value namespace_interface.
</longdesc>
<shortdesc lang="en">List of addtional routes to add routes for.</shortdesc>
<content type="string" default="$OCF_RESKEY_other_networks_default"/>
</parameter>

<parameter name="host_interface">
<longdesc lang="en">
The host part of the interface pair used to connect the namespace to the network
For example, "vrouter-host"
</longdesc>
<shortdesc lang="en">The name of the host interface used for namespace</shortdesc>
<content type="string" default="${OCF_RESKEY_host_interface_default}" />
</parameter>

<parameter name="namespace_interface">
<longdesc lang="en">
The namespace part of the interface pair used to connect the namespace to the network
For example, "vr-ns"
</longdesc>
<shortdesc lang="en">The name of the namespace interface used for namespace</shortdesc>
<content type="string" default="${OCF_RESKEY_namespace_interface_default}" />
</parameter>

<parameter name="host_ip">
<longdesc lang="en">
The IP address used by the host interface. Must be from the same subnet as namesapce IP
and uses network_mask to determine subnet.
Should not collide with any IP addresses already used in your network.
For example, "240.0.0.5"
</longdesc>
<shortdesc lang="en">Host interface IP address</shortdesc>
<content type="string" default="${OCF_RESKEY_host_ip_default}" />
</parameter>

<parameter name="namespace_ip">
<longdesc lang="en">
The IP address used by the namespace interface. Must be from the same subnet as host IP
and uses network_mask to determine subnet.
Should not collide with any IP addresses already used in your network.
For example, "240.0.0.6"
</longdesc>
<shortdesc lang="en">Namespace interface IP address</shortdesc>
<content type="string" default="${OCF_RESKEY_namespace_ip_default}" />
</parameter>

<parameter name="network_mask">
<longdesc lang="en">
The network mask length used to determine subnet of the host
and the namspace interfaces.
For example, "30"
</longdesc>
<shortdesc lang="en">Network mask length</shortdesc>
<content type="string" default="${OCF_RESKEY_network_mask_default}" />
</parameter>

<parameter name="route_metric">
<longdesc lang="en">
The metric value of the default route set for the pipe
link connecting namespace and host. It should be set to
a large number to be higher then other default route metrics
that could be set to override this default route.
If other routes are set eithin the namespace thir metric should
be smaller then this number if you want them to be used istead of
this route.
For example, "1000"
</longdesc>
<shortdesc lang="en">Namespace default route metric</shortdesc>
<content type="string" default="${OCF_RESKEY_route_metric_default}" />
</parameter>

</parameters>

<actions>
<action name="start" timeout="20s"/>
<action name="stop" timeout="20s"/>
<action name="reload" timeout="20s"/>
<action name="monitor" depth="0" timeout="20s" interval="60s" />
<action name="validate-all" timeout="20s"/>
<action name="meta-data"  timeout="5s"/>
</actions>
</resource-agent>
END
exit $OCF_SUCCESS
}

check_ns() {
  local LH="${LL} check_ns():"
  local ns=`ip netns list | grep "$OCF_RESKEY_ns"`
  ocf_log debug "${LH} recieved netns list: ${ns}"
  [[ $ns != $OCF_RESKEY_ns ]] && return $OCF_ERR_GENERIC
  return $OCF_SUCCESS
}

get_ns() {
  local rc
  local LH="${LL} get_ns():"
  check_ns && return $OCF_SUCCESS

  ocf_run ip netns add $OCF_RESKEY_ns
  rc=$?
  ocf_run $RUN_IN_NS ip link set up dev lo
  ocf_log debug "${LH} added netns ${OCF_RESKEY_ns} and set up lo"

  return $rc
}

set_ns_routing() {
  nsip() {
    ip netns exec "${OCF_RESKEY_ns}" ip ${@}
  }

  # create host-ns veth pair unless it's present
  ip link | grep -q "${OCF_RESKEY_host_interface}:"
  if [ $? -gt 0 ]; then
    ocf_log debug "Creating host interface: ${OCF_RESKEY_host_interface} and namespace interface: ${OCF_RESKEY_namespace_interface}"
    ocf_run ip link add "${OCF_RESKEY_host_interface}" type veth peer name "${OCF_RESKEY_namespace_interface}"
  else
    return $OCF_SUCCESS
  fi

  # move the ns part to the namespace
  ip link | grep -q "${OCF_RESKEY_namespace_interface}:"
  if [ $? -eq 0 ]; then
    ocf_log debug "Moving interface: ${OCF_RESKEY_namespace_interface} to namespace: ${OCF_RESKEY_ns}"
    ocf_run ip link set dev "${OCF_RESKEY_namespace_interface}" netns "${OCF_RESKEY_ns}"
  fi

  # up the host part
  ocf_log debug "Bringing up host interface: ${OCF_RESKEY_host_interface}"
  ocf_run ip link set "${OCF_RESKEY_host_interface}" up

  # set host part's ip
  ip addr show dev "${OCF_RESKEY_host_interface}" | grep -q "inet ${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}"
  if [ $? -gt 0 ]; then
    ocf_log debug "Setting host interface: ${OCF_RESKEY_host_interface} IP to: ${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}"
    ocf_run ip addr add "${OCF_RESKEY_host_ip}/${OCF_RESKEY_network_mask}" dev "${OCF_RESKEY_host_interface}"
  fi

  # up the ns part
  ocf_log debug "Bringing up the namespace interface: ${OCF_RESKEY_namespace_interface}"
  ocf_run nsip link set "${OCF_RESKEY_namespace_interface}" up

  # set ns part's ip
  nsip addr show dev "${OCF_RESKEY_namespace_interface}" | grep -q "inet ${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}"
  if [ $? -gt 0 ]; then
    ocf_log debug "Setting namespace interface: ${OCF_RESKEY_namespace_interface} IP to: ${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}"
    ocf_run nsip addr add "${OCF_RESKEY_namespace_ip}/${OCF_RESKEY_network_mask}" dev "${OCF_RESKEY_namespace_interface}"
  fi

  # set default gateway inside ns
  nsip route list | grep -q "default via ${OCF_RESKEY_host_ip}"
  if [ $? -gt 0 ]; then
    ocf_log debug "Creating default route inside the namespace to ${OCF_RESKEY_host_ip} with metric ${OCF_RESKEY_route_metric}"
    ocf_run nsip route add default via "${OCF_RESKEY_host_ip}" metric "${OCF_RESKEY_route_metric}"
  fi

  # set masquerade on host node
  iptables -t nat -L | grep -q masquerade-for-vrouter-namespace
  if [ $? -gt 0 ]; then
    ocf_log debug "Creating NAT rule on the host system for traffic from IP: ${OCF_RESKEY_namespace_ip}"
    ocf_run iptables -t nat -A POSTROUTING -s "${OCF_RESKEY_namespace_ip}" -j MASQUERADE -m comment --comment "masquerade-for-vrouter-namespace"
  fi

  ### Needed for ML2 routing ###
  ocf_run sysctl -w net.ipv4.conf.${OCF_RESKEY_host_interface}.rp_filter=2
  ocf_run $RUN_IN_NS sysctl -w net.ipv4.conf.all.rp_filter=2
  ##############################

  if [[ "${OCF_RESKEY_other_networks}" != "false" ]] ; then
    for network in ${OCF_RESKEY_other_networks}
    do
      ocf_log debug "Adding route on the host system to ${network}: ${OCF_RESKEY_namespace_ip}"
      ocf_run $RUN_IN_NS ip route replace ${network} via ${OCF_RESKEY_host_ip} metric 10000
    done
  fi
}

vrouter_status() {
  get_ns || return $OCF_NOT_RUNNING
  set_ns_routing
}

vrouter_start()
{
  get_ns
  set_ns_routing
        return $OCF_SUCCESS
}

vrouter_stop()
{
        ocf_log debug "Vrouter was stopped, namespaces still exist"
  return $OCF_SUCCESS
}

vrouter_monitor()
{
  vrouter_status
}

vrouter_validate_all()
{
  get_ns
  return $OCF_SUCCESS
}

vrouter_restart()
{
  vrouter_stop
  vrouter_start
}

#
# Main
#

if [ $# -ne 1 ]; then
  usage
  exit $OCF_ERR_ARGS
fi
umask 0022
export LL="${OCF_RESOURCE_INSTANCE}:"

case $1 in
  start) vrouter_start
  ;;

  stop) vrouter_stop
  ;;

  restart) vrouter_restart
  ;;

  status)  vrouter_status
  ;;

  monitor) vrouter_monitor
  ;;

  validate-all) vrouter_validate_all
  ;;

  meta-data) meta_data
  ;;

  usage) usage; exit $OCF_SUCCESS
  ;;

  *) usage; exit $OCF_ERR_UNIMPLEMENTED
  ;;
esac
